#include "os_sockets.h"
#include "hash.h"

static hash_t ip_hash;
static hash_t udp_hash;
static bool hash_init = FALSE;

// Init device
err_t init_sockets(void)
{
    err_t hash_err;

    if (hash_init) {
        return ERR_SUCCESS;
    }
    
    hash_err = create_hash(&ip_hash, 128, HASH_TYPE_NUMERIC);
    if (hash_err != ERR_SUCCESS) {
        printf("ip hash creation failed: 0x%x\n", hash_err);
	return ERR_INTERNAL_ERROR;
    }

    hash_err = create_hash(&udp_hash, 128, HASH_TYPE_NUMERIC);
    if (hash_err != ERR_SUCCESS) {
        printf("udp hash creation failed: 0x%x\n", hash_err);
	return ERR_INTERNAL_ERROR;
    }

    hash_init = TRUE;
    return ERR_SUCCESS;
}

static inline
err_t validate_device_params(dev_info_t *device_info, hash_t **hash)
{
    hash_t *hash_ptr = NULL;

    if (!device_info) {
        return ERR_INVALID;
    }

    if (device_info->subtype == DEVICE_SUBTYPE_IP_SOCKET) {
        hash_ptr = &ip_hash;
    } else if (device_info->subtype == DEVICE_SUBTYPE_UDP_SOCKET) {
        hash_ptr = &udp_hash;
    } else {
        // Unsupported socket
	printf("Invalid subtype 0x%x, not supported\n", device_info->subtype);
	return ERR_NOT_SUPPORTED;
    }

    if (device_info->address > 0xFFFF || device_info->address == 0) {
        // port numbers should be only 16 bit
	return ERR_INVALID;
    }

    if (hash) {
        *hash = hash_ptr;
    }

    return ERR_SUCCESS;
 }


err_t lookup_socket(dev_info_t *device_info, device_t **device)
{
    hash_t *hash_ptr = NULL;
    device_t *dev = NULL;
    err_t dev_error;

    if (!device) {
        return ERR_INVALID;
    }

    dev_error = validate_device_params(device_info, &hash_ptr);
    if (dev_error != ERR_SUCCESS) {
        return dev_error;
    }

    lookup_hash(hash_ptr, &device_info->address, (uint32_t *)&dev);
    *device = dev;

    return ERR_SUCCESS;
}



err_t open_socket(dev_handle_t *handle, device_t **device)
{
    hash_t *hash_ptr = NULL; 
    device_t *dev = NULL;
    err_t dev_error;

    if (!handle || !device) {
        return ERR_INVALID;
    }

    dev_error = validate_device_params(&handle->dev_info, &hash_ptr);
    if (dev_error != ERR_SUCCESS) {
        return dev_error;
    }

    if (handle->open_mode != DEVICE_MODE_CREATE_ALWAYS) {
        return ERR_INVALID_MODE;
    }

    // lookup whether the specified socket already exists
    lookup_hash(hash_ptr, &handle->dev_info.address, (uint32_t *)&dev);
    if (dev) {
        // Socket is already open
        return ERR_ALREADY_EXIST;
    }

    // Create device
    dev = (device_t *)calloc(1, sizeof(device_t));
    if (!dev) {
        return ERR_NO_MEM;
    }

    dev->device_info = handle->dev_info;
    dev->device_info.name = NULL;   // names not required for sockets
    dev->mode =  0; // ??
    dev->state = DEVICE_STATE_OPENED_READ_WRITE;
    dev->num_writers = dev->ref_count = 1;

    if (insert_hash(hash_ptr, &handle->dev_info.address, (uint32_t)dev) != ERR_SUCCESS) {
        printf("Failed inserting socket device into hash\n");
	free(dev);
	return ERR_INTERNAL_ERROR;
    }
    
    handle->device_created = TRUE;
    *device = dev;

    return ERR_SUCCESS;
}


err_t close_socket(dev_access_t *access_info)
{
    device_t *dev = NULL;
    device_t *stored_dev = NULL;
    err_t dev_error;
    hash_t *hash_ptr = NULL;
    err_t hash_err;

    if (!access_info || !access_info->dev_ptr) {
        return ERR_INVALID;
    }

    dev = (device_t *)access_info->dev_ptr;
    dev_error = lookup_socket(&dev->device_info, &stored_dev);
    if (dev_error != ERR_SUCCESS || dev != stored_dev) {
        return ERR_INVALID;
    }

    hash_ptr = (dev->device_info.subtype == DEVICE_SUBTYPE_UDP_SOCKET) ? &udp_hash : &ip_hash;

    hash_err = remove_hash_node(hash_ptr, &dev->device_info.address);
    if (hash_err != ERR_SUCCESS) {
        return ERR_INTERNAL_ERROR;
    }

    free(dev);
    
    return ERR_SUCCESS;

}
